<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/blog/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/blog/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/blog/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/blog/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/blog/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/blog/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/blog/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="PWA学习与实践,">





  <link rel="alternate" href="/blog/atom.xml" title="编程那点事" type="application/atom+xml">






<meta name="description" content="一、背景大家都知道Native app体验确实很好，下载到手机上之后入口也方便。它也有一些缺点:  开发成本高(ios和安卓) 软件上线需要审核 版本更新需要将新版本上传到不同的应用商店 想使用一个app就必须去下载才能使用，即使是偶尔需要使用一下下  而web网页开发成本低，网站更新时上传最新的资源到服务器即可，用手机带的浏览器打开就可以使用。但是出了体验上比Native app还是差一些，还有">
<meta name="keywords" content="PWA学习与实践">
<meta property="og:type" content="article">
<meta property="og:title" content="【PWA学习与实践】(2) PWA的实现">
<meta property="og:url" content="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/index.html">
<meta property="og:site_name" content="编程那点事">
<meta property="og:description" content="一、背景大家都知道Native app体验确实很好，下载到手机上之后入口也方便。它也有一些缺点:  开发成本高(ios和安卓) 软件上线需要审核 版本更新需要将新版本上传到不同的应用商店 想使用一个app就必须去下载才能使用，即使是偶尔需要使用一下下  而web网页开发成本低，网站更新时上传最新的资源到服务器即可，用手机带的浏览器打开就可以使用。但是出了体验上比Native app还是差一些，还有">
<meta property="og:locale" content="default">
<meta property="og:image" content="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/4032102925-5a2b5f6fec25a_articlex.png">
<meta property="og:image" content="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/20180928083222.png">
<meta property="og:image" content="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/1448556198-5a2b5f702c485_articlex.png">
<meta property="og:image" content="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/2894994167-5a2b5f703e61d_articlex.png">
<meta property="og:image" content="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/2113064281-5a2b5f700af67_articlex.png">
<meta property="og:updated_time" content="2019-10-03T05:53:45.936Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="【PWA学习与实践】(2) PWA的实现">
<meta name="twitter:description" content="一、背景大家都知道Native app体验确实很好，下载到手机上之后入口也方便。它也有一些缺点:  开发成本高(ios和安卓) 软件上线需要审核 版本更新需要将新版本上传到不同的应用商店 想使用一个app就必须去下载才能使用，即使是偶尔需要使用一下下  而web网页开发成本低，网站更新时上传最新的资源到服务器即可，用手机带的浏览器打开就可以使用。但是出了体验上比Native app还是差一些，还有">
<meta name="twitter:image" content="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/4032102925-5a2b5f6fec25a_articlex.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/blog/',
    scheme: 'Muse',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: 'Author'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://lhweb.gitee.io/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/">





  <title>【PWA学习与实践】(2) PWA的实现 | 编程那点事</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="default">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/blog/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">编程那点事</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/blog/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            Home
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/blog/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            Archives
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://lhweb.gitee.io/blog/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="lhweb">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/blog/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="编程那点事">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">【PWA学习与实践】(2) PWA的实现</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">Posted on</span>
              
              <time title="Post created" itemprop="dateCreated datePublished" datetime="2018-09-28T08:23:08+08:00">
                2018-09-28
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">In</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/blog/categories/pwa/" itemprop="url" rel="index">
                    <span itemprop="name">pwa</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h1 id="一、背景"><a href="#一、背景" class="headerlink" title="一、背景"></a>一、背景</h1><p>大家都知道Native app体验确实很好，下载到手机上之后入口也方便。它也有一些缺点:</p>
<ul>
<li>开发成本高(ios和安卓)</li>
<li>软件上线需要审核</li>
<li>版本更新需要将新版本上传到不同的应用商店</li>
<li>想使用一个app就必须去下载才能使用，即使是偶尔需要使用一下下</li>
</ul>
<p>而web网页开发成本低，网站更新时上传最新的资源到服务器即可，用手机带的浏览器打开就可以使用。但是出了体验上比Native app还是差一些，还有一些明显的缺点</p>
<ul>
<li>手机桌面入口不够便捷，想要进入一个页面必须要记住它的url或者加入书签</li>
<li>没网络就没响应，不具备离线能力</li>
<li>不像APP一样能进行消息推送</li>
</ul>
<p>那么什么是PWA呢？</p>
<h1 id="二、What’s-PWA"><a href="#二、What’s-PWA" class="headerlink" title="二、What’s PWA?"></a>二、What’s PWA?</h1><p>PWA全称Progressive Web App，即渐进式WEB应用。</p>
<p>一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能<br>解决了哪些问题？</p>
<ul>
<li>可以添加至主屏幕，点击主屏幕图标可以实现启动动画以及隐藏地址栏</li>
<li>实现离线缓存功能，即使用户手机没有网络，依然可以使用一些离线功能</li>
<li>实现了消息推送</li>
</ul>
<p>它解决了上述提到的问题，这些特性将使得 Web 应用渐进式接近原生 App。</p>
<h1 id="三、PWA的实现"><a href="#三、PWA的实现" class="headerlink" title="三、PWA的实现"></a>三、PWA的实现</h1><h2 id="Manifest实现添加至主屏幕"><a href="#Manifest实现添加至主屏幕" class="headerlink" title="Manifest实现添加至主屏幕"></a>Manifest实现添加至主屏幕</h2><p>index.html</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>Minimal PWA<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">"viewport"</span> <span class="attr">content</span>=<span class="string">"width=device-width, user-scalable=no"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"manifest"</span> <span class="attr">href</span>=<span class="string">"manifest.json"</span> /&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">type</span>=<span class="string">"text/css"</span> <span class="attr">href</span>=<span class="string">"main.css"</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"icon"</span> <span class="attr">href</span>=<span class="string">"/e.png"</span> <span class="attr">type</span>=<span class="string">"image/png"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>manifest.json</p>
<figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"name"</span>: <span class="string">"Minimal PWA"</span>, <span class="comment">// 必填 显示的插件名称</span></span><br><span class="line">  <span class="string">"short_name"</span>: <span class="string">"PWA Demo"</span>, <span class="comment">// 可选  在APP launcher和新的tab页显示，如果没有设置，则使用name</span></span><br><span class="line">  <span class="string">"description"</span>: <span class="string">"The app that helps you understand PWA"</span>, <span class="comment">//用于描述应用</span></span><br><span class="line">  <span class="string">"display"</span>: <span class="string">"standalone"</span>, <span class="comment">// 定义开发人员对Web应用程序的首选显示模式。standalone模式会有单独的</span></span><br><span class="line">  <span class="string">"start_url"</span>: <span class="string">"/"</span>, <span class="comment">// 应用启动时的url</span></span><br><span class="line">  <span class="string">"theme_color"</span>: <span class="string">"#313131"</span>, <span class="comment">// 桌面图标的背景色</span></span><br><span class="line">  <span class="string">"background_color"</span>: <span class="string">"#313131"</span>, <span class="comment">// 为web应用程序预定义的背景颜色。在启动web应用程序和加载应用程序的内容之间创建了一个平滑的过渡。</span></span><br><span class="line">  <span class="string">"icons"</span>: [ <span class="comment">// 桌面图标，是一个数组</span></span><br><span class="line">    &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/lowres.webp"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"48x48"</span>,  <span class="comment">// 以空格分隔的图片尺寸</span></span><br><span class="line">    <span class="string">"type"</span>: <span class="string">"image/webp"</span>  <span class="comment">// 帮助userAgent快速排除不支持的类型</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/lowres"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"48x48"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/hd_hi.ico"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"72x72 96x96 128x128 256x256"</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="string">"src"</span>: <span class="string">"icon/hd_hi.svg"</span>,</span><br><span class="line">    <span class="string">"sizes"</span>: <span class="string">"72x72"</span></span><br><span class="line">  &#125;</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Manifest参考文档：<a href="https://developer.mozilla.org/zh-CN/docs/Web/Manifest" target="_blank" rel="noopener">https://developer.mozilla.org/zh-CN/docs/Web/Manifest</a></p>
</blockquote>
<blockquote>
<p>可以打开网站<a href="https://developers.google.cn/web/showcase/2015/chrome-dev-summit" target="_blank" rel="noopener">https://developers.google.cn/web/showcase/2015/chrome-dev-summit</a>查看添加至主屏幕的动图。</p>
</blockquote>
<blockquote>
<p>如果用的是安卓手机，可以下载chrome浏览器自己操作看看</p>
</blockquote>
<h2 id="service-worker实现离线缓存"><a href="#service-worker实现离线缓存" class="headerlink" title="service worker实现离线缓存"></a>service worker实现离线缓存</h2><h3 id="什么是service-worker"><a href="#什么是service-worker" class="headerlink" title="什么是service worker"></a>什么是service worker</h3><p>Service Worker 是 Chrome 团队提出和力推的一个 WEB API，用于给 web 应用提供高级的可持续的后台处理能力。</p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/4032102925-5a2b5f6fec25a_articlex.png" alt></p>
<p>Service Workers 就像介于服务器和网页之间的拦截器，能够拦截进出的HTTP 请求，从而完全控制你的网站。</p>
<p><strong>最主要的特点</strong></p>
<ul>
<li>在页面中注册并安装成功后，运行于浏览器后台，不受页面刷新的影响，可以监听和截拦作用域范围内所有页面的 HTTP 请求。</li>
<li>网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)</li>
<li>运行于浏览器后台，可以控制打开的作用域范围下所有的页面请求</li>
<li>单独的作用域范围，单独的运行环境和执行线程</li>
<li>不能操作页面 DOM。但可以通过事件机制来处理</li>
<li>事件驱动型服务线程</li>
</ul>
<blockquote>
<p>为什么要求网站必须是HTTPS的，大概是因为service worker权限太大能拦截所有页面的请求吧，如果http的网站安装service worker很容易被攻击</p>
</blockquote>
<p><strong>浏览器支持情况</strong></p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/20180928083222.png" alt></p>
<blockquote>
<p>浏览器支持情况详见： <a href="https://caniuse.com/#feat=serviceworkers" target="_blank" rel="noopener">https://caniuse.com/#feat=serviceworkers</a></p>
</blockquote>
<p><strong>生命周期</strong></p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/1448556198-5a2b5f702c485_articlex.png" alt></p>
<p>当用户首次导航至 URL 时，服务器会返回响应的网页。</p>
<ul>
<li>第1步:当你调用 register() 函数时， Service Worker 开始下载。</li>
<li>第2步:在注册过程中，浏览器会下载、解析并执行 Service Worker ()。如果在此步骤中出现任何错误，register() 返回的 promise 都会执行 reject 操作，并且 Service Worker 会被废弃。</li>
<li>第3步:一旦 Service Worker 成功执行了，install 事件就会激活</li>
<li>第4步:安装完成，Service Worker 便会激活，并控制在其范围内的一切。如果生命周期中的所有事件都成功了，Service Worker 便已准备就绪，随时可以使用了！</li>
</ul>
<blockquote>
<p><a href="chrome://serviceworker-internals" target="_blank" rel="noopener">chrome://serviceworker-internals</a> 来了解当前浏览器中所有已安装Service Worker的详细情况</p>
</blockquote>
<h3 id="HTTP缓存与service-worker缓存"><a href="#HTTP缓存与service-worker缓存" class="headerlink" title="HTTP缓存与service worker缓存"></a>HTTP缓存与service worker缓存</h3><ul>
<li>HTTP缓存</li>
</ul>
<p>Web 服务器可以使用 Expires 首部来通知 Web 客户端，它可以使用资源的当前副本，直到指定的“过期时间”。反过来，浏览器可以缓存此资源，并且只有在有效期满后才会再次检查新版本。<br>使用 HTTP 缓存意味着你要依赖服务器来告诉你何时缓存资源和何时过期。</p>
<ul>
<li>service worker缓存</li>
</ul>
<p>Service Workers 的强大在于它们拦截 HTTP 请求的能力<br>进入任何传入的 HTTP 请求，并决定想要如何响应。在你的 Service Worker 中，可以编写逻辑来决定想要缓存的资源，以及需要满足什么条件和资源需要缓存多久。一切尽归你掌控*</p>
<h3 id="实现离线缓存"><a href="#实现离线缓存" class="headerlink" title="实现离线缓存"></a>实现离线缓存</h3><p>index.html</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Hello Caching World!<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Image --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">"/images/hello.png"</span> /&gt;</span>                 </span><br><span class="line">    <span class="comment">&lt;!-- JavaScript --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">async</span> <span class="attr">src</span>=<span class="string">"/js/script.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span>     </span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="actionscript">      <span class="comment">// 注册 service worker</span></span></span><br><span class="line"><span class="actionscript">      <span class="keyword">if</span> (<span class="string">'serviceWorker'</span> <span class="keyword">in</span> navigator) &#123;           </span></span><br><span class="line"><span class="actionscript">        navigator.serviceWorker.register(<span class="string">'/service-worker.js'</span>, &#123;scope: <span class="string">'/'</span>&#125;).then(<span class="function"><span class="keyword">function</span> <span class="params">(registration)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">          <span class="comment">// 注册成功</span></span></span><br><span class="line"><span class="javascript">          <span class="built_in">console</span>.log(<span class="string">'ServiceWorker registration successful with scope: '</span>, registration.scope);</span></span><br><span class="line"><span class="actionscript">        &#125;).catch(<span class="function"><span class="keyword">function</span> <span class="params">(err)</span> </span>&#123;                   </span></span><br><span class="line"><span class="actionscript">          <span class="comment">// 注册失败 :(</span></span></span><br><span class="line"><span class="javascript">          <span class="built_in">console</span>.log(<span class="string">'ServiceWorker registration failed: '</span>, err);</span></span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>注：Service Worker 的注册路径决定了其 scope 默认作用页面的范围。<br>如果 service-worker.js 是在 /sw/ 页面路径下，这使得该 Service Worker 默认只会收到 页面/sw/ 路径下的 fetch 事件。<br>如果存放在网站的根路径下，则将会收到该网站的所有 fetch 事件。<br>如果希望改变它的作用域，可在第二个参数设置 scope 范围。示例中将其改为了根目录，即对整个站点生效。</p>
</blockquote>
<p>service-worker.js</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> cacheName = <span class="string">'helloWorld'</span>;     <span class="comment">// 缓存的名称  </span></span><br><span class="line"><span class="comment">// install 事件，它发生在浏览器安装并注册 Service Worker 时        </span></span><br><span class="line"><span class="keyword">self</span>.addEventListener(<span class="string">'install'</span>, event =&gt; &#123; </span><br><span class="line"><span class="comment">/* event.waitUtil 用于在安装成功之前执行一些预装逻辑</span></span><br><span class="line"><span class="comment"> 但是建议只做一些轻量级和非常重要资源的缓存，减少安装失败的概率</span></span><br><span class="line"><span class="comment"> 安装成功后 ServiceWorker 状态会从 installing 变为 installed */</span></span><br><span class="line">  event.waitUntil(</span><br><span class="line">    caches.open(cacheName)                  </span><br><span class="line">    .then(cache =&gt; cache.addAll([    <span class="comment">// 如果所有的文件都成功缓存了，便会安装完成。如果任何文件下载失败了，那么安装过程也会随之失败。        </span></span><br><span class="line">      <span class="string">'/js/script.js'</span>,</span><br><span class="line">      <span class="string">'/images/hello.png'</span></span><br><span class="line">    ]))</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br><span class="line">  </span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">为 fetch 事件添加一个事件监听器。接下来，使用 caches.match() 函数来检查传入的请求 URL 是否匹配当前缓存中存在的任何内容。如果存在的话，返回缓存的资源。</span></span><br><span class="line"><span class="comment">如果资源并不存在于缓存当中，通过网络来获取资源，并将获取到的资源添加到缓存中。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">self</span>.addEventListener(<span class="string">'fetch'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(event)</span> </span>&#123;</span><br><span class="line">  event.respondWith(</span><br><span class="line">    caches.match(event.request)                  </span><br><span class="line">    .then(<span class="function"><span class="keyword">function</span> <span class="params">(response)</span> </span>&#123;</span><br><span class="line">      <span class="keyword">if</span> (response) &#123;                            </span><br><span class="line">        <span class="keyword">return</span> response;                         </span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">var</span> requestToCache = event.request.<span class="keyword">clone</span>();  <span class="comment">//          </span></span><br><span class="line">      <span class="keyword">return</span> fetch(requestToCache).then(                   </span><br><span class="line">        <span class="function"><span class="keyword">function</span> <span class="params">(response)</span> </span>&#123;</span><br><span class="line">          <span class="keyword">if</span> (!response || response.status !== <span class="number">200</span>) &#123;      </span><br><span class="line">            <span class="keyword">return</span> response;</span><br><span class="line">          &#125;</span><br><span class="line">          <span class="keyword">var</span> responseToCache = response.<span class="keyword">clone</span>();          </span><br><span class="line">          caches.open(cacheName)                           </span><br><span class="line">            .then(<span class="function"><span class="keyword">function</span> <span class="params">(cache)</span> </span>&#123;</span><br><span class="line">              cache.put(requestToCache, responseToCache);  </span><br><span class="line">            &#125;);</span><br><span class="line">          <span class="keyword">return</span> response;             </span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>注：为什么用request.clone()和response.clone()<br>需要这么做是因为request和response是一个流，它只能消耗一次。因为我们已经通过缓存消耗了一次，然后发起 HTTP 请求还要再消耗一次，所以我们需要在此时克隆请求<br>Clone the request—a request is a stream and can only be consumed once.</p>
</blockquote>
<h3 id="调试相关"><a href="#调试相关" class="headerlink" title="调试相关"></a>调试相关</h3><p>chrome浏览器打开<a href="https://googlechrome.github.io/samples/service-worker/basic/index.html" target="_blank" rel="noopener">https://googlechrome.github.io/samples/service-worker/basic/index.html</a>，这是一个实现了service worker离线缓存功能的网站，打开调试工具</p>
<p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/2894994167-5a2b5f703e61d_articlex.png" alt></p>
<p>介绍一个图中的1.和2.</p>
<ol>
<li>勾选可以模拟网站离线情况，勾选后network会有一个黄色警告图标，该网站已经离线。此时刷新页面，页面仍然能够正常显示</li>
<li>当前service worker的scope。它能够拦截<a href="https://googlechrome.github.i...，同样也能够拦截https://googlechrome.github.i...*/*.html下的请求" target="_blank" rel="noopener">https://googlechrome.github.i...，同样也能够拦截https://googlechrome.github.i...*/*.html下的请求</a></li>
</ol>
<blockquote>
<p>调试面板具体代表的什么参看<a href="https://x5.tencent.com/tbs/guide/serviceworker.html" target="_blank" rel="noopener">https://x5.tencent.com/tbs/guide/serviceworker.html</a>的第三部分</p>
</blockquote>
<h3 id="serice-worker实现消息推送"><a href="#serice-worker实现消息推送" class="headerlink" title="serice worker实现消息推送"></a>serice worker实现消息推送</h3><p><img src="/blog/2018/09/28/【PWA学习与实践】-2-PWA的实现/2113064281-5a2b5f700af67_articlex.png" alt></p>
<ul>
<li>步骤一、提示用户并获得他们的订阅详细信息</li>
<li>步骤二、将这些详细信息保存在服务器上</li>
<li>步骤三、在需要时发送任何消息</li>
</ul>
<blockquote>
<p>不同浏览器需要用不同的推送消息服务器。以 Chrome 上使用 Google Cloud Messaging<gcm> 作为推送服务为例，第一步是注册 applicationServerKey(通过 GCM 注册获取)，并在页面上进行订阅或发起订阅。每一个会话会有一个独立的端点（endpoint），订阅对象的属性(PushSubscription.endpoint) 即为端点值。将端点发送给服务器后，服务器用这一值来发送消息给会话的激活的 Service Worker （通过 GCM 与浏览器客户端沟通）。</gcm></p>
</blockquote>
<p><strong>步骤一和步骤二</strong></p>
<p>index.html</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Progressive Times<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"manifest"</span> <span class="attr">href</span>=<span class="string">"/manifest.json"</span>&gt;</span>                                      </span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> endpoint;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> key;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> authSecret;</span></span><br><span class="line"><span class="actionscript">      <span class="keyword">var</span> vapidPublicKey = <span class="string">'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY'</span>;</span></span><br><span class="line"><span class="actionscript">      <span class="comment">// 方法很复杂，但是可以不用具体看，知识用来转化vapidPublicKey用</span></span></span><br><span class="line"><span class="actionscript">      <span class="function"><span class="keyword">function</span> <span class="title">urlBase64ToUint8Array</span><span class="params">(base64String)</span> </span>&#123;                                  </span></span><br><span class="line"><span class="actionscript">        <span class="keyword">const</span> padding = <span class="string">'='</span>.repeat((<span class="number">4</span> - base64String.length % <span class="number">4</span>) % <span class="number">4</span>);</span></span><br><span class="line"><span class="actionscript">        <span class="keyword">const</span> base64 = (base64String + padding)</span></span><br><span class="line"><span class="javascript">          .replace(<span class="regexp">/\-/g</span>, <span class="string">'+'</span>)</span></span><br><span class="line"><span class="javascript">          .replace(<span class="regexp">/_/g</span>, <span class="string">'/'</span>);</span></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> rawData = <span class="built_in">window</span>.atob(base64);</span></span><br><span class="line"><span class="javascript">        <span class="keyword">const</span> outputArray = <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(rawData.length);</span></span><br><span class="line"><span class="javascript">        <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; rawData.length; ++i) &#123;</span></span><br><span class="line">          outputArray[i] = rawData.charCodeAt(i);</span><br><span class="line">        &#125;</span><br><span class="line"><span class="actionscript">        <span class="keyword">return</span> outputArray;</span></span><br><span class="line">      &#125;</span><br><span class="line"><span class="actionscript">      <span class="keyword">if</span> (<span class="string">'serviceWorker'</span> <span class="keyword">in</span> navigator) &#123;</span></span><br><span class="line"><span class="actionscript">        navigator.serviceWorker.register(<span class="string">'sw.js'</span>).then(<span class="function"><span class="keyword">function</span> <span class="params">(registration)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">          <span class="keyword">return</span> registration.pushManager.getSubscription()                            </span></span><br><span class="line"><span class="actionscript">            .then(<span class="function"><span class="keyword">function</span> <span class="params">(subscription)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">              <span class="keyword">if</span> (subscription) &#123;                                                      </span></span><br><span class="line"><span class="actionscript">                <span class="keyword">return</span>;</span></span><br><span class="line">              &#125;</span><br><span class="line"><span class="actionscript">              <span class="keyword">return</span> registration.pushManager.subscribe(&#123;                              </span></span><br><span class="line"><span class="actionscript">                  userVisibleOnly: <span class="literal">true</span>,</span></span><br><span class="line">                  applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)</span><br><span class="line">                &#125;)</span><br><span class="line"><span class="actionscript">                .then(<span class="function"><span class="keyword">function</span> <span class="params">(subscription)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">                  <span class="keyword">var</span> rawKey = subscription.getKey ? subscription.getKey(<span class="string">'p256dh'</span>) : <span class="string">''</span>;</span></span><br><span class="line"><span class="javascript">                  key = rawKey ? btoa(<span class="built_in">String</span>.fromCharCode.apply(<span class="literal">null</span>, <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(rawKey))) : <span class="string">''</span>;</span></span><br><span class="line"><span class="actionscript">                  <span class="keyword">var</span> rawAuthSecret = subscription.getKey ? subscription.getKey(<span class="string">'auth'</span>) : <span class="string">''</span>;</span></span><br><span class="line">                  authSecret = rawAuthSecret ?</span><br><span class="line"><span class="javascript">                    btoa(<span class="built_in">String</span>.fromCharCode.apply(<span class="literal">null</span>, <span class="keyword">new</span> <span class="built_in">Uint8Array</span>(rawAuthSecret))) : <span class="string">''</span>;</span></span><br><span class="line">                  endpoint = subscription.endpoint;</span><br><span class="line"><span class="actionscript">                  <span class="keyword">return</span> fetch(<span class="string">'./register'</span>, &#123;                                         </span></span><br><span class="line"><span class="actionscript">                    method: <span class="string">'post'</span>,</span></span><br><span class="line"><span class="actionscript">                    headers: <span class="keyword">new</span> Headers(&#123;</span></span><br><span class="line"><span class="actionscript">                      <span class="string">'content-type'</span>: <span class="string">'application/json'</span></span></span><br><span class="line">                    &#125;),</span><br><span class="line"><span class="javascript">                    body: <span class="built_in">JSON</span>.stringify(&#123;</span></span><br><span class="line">                      endpoint: subscription.endpoint,</span><br><span class="line">                      key: key,</span><br><span class="line">                      authSecret: authSecret,</span><br><span class="line">                    &#125;),</span><br><span class="line">                  &#125;);</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;);</span><br><span class="line"><span class="actionscript">        &#125;).catch(<span class="function"><span class="keyword">function</span> <span class="params">(err)</span> </span>&#123;</span></span><br><span class="line"><span class="actionscript">          <span class="comment">// 注册失败 :(</span></span></span><br><span class="line"><span class="javascript">          <span class="built_in">console</span>.log(<span class="string">'ServiceWorker registration failed: '</span>, err);</span></span><br><span class="line">        &#125;);</span><br><span class="line">      &#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p><strong>步骤三 服务器发送消息给service worker</strong></p>
<p>app.js</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> webpush = <span class="built_in">require</span>(<span class="string">'web-push'</span>);                 </span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">var</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>);</span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line">webpush.setVapidDetails(                             </span><br><span class="line">  <span class="string">'mailto:contact@deanhume.com'</span>,</span><br><span class="line">  <span class="string">'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY'</span>,</span><br><span class="line">  <span class="string">'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'</span></span><br><span class="line">);</span><br><span class="line">app.post(<span class="string">'/register'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;           </span><br><span class="line">  <span class="keyword">var</span> endpoint = req.body.endpoint;</span><br><span class="line">  saveRegistrationDetails(endpoint, key, authSecret); </span><br><span class="line">  <span class="keyword">const</span> pushSubscription = &#123;                          </span><br><span class="line">    endpoint: req.body.endpoint,</span><br><span class="line">    keys: &#123;</span><br><span class="line">      auth: req.body.authSecret,</span><br><span class="line">      p256dh: req.body.key</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="keyword">var</span> body = <span class="string">'Thank you for registering'</span>;</span><br><span class="line">  <span class="keyword">var</span> iconUrl = <span class="string">'https://example.com/images/homescreen.png'</span>;</span><br><span class="line">  <span class="comment">// 发送 Web 推送消息</span></span><br><span class="line">  webpush.sendNotification(pushSubscription,          </span><br><span class="line">      <span class="built_in">JSON</span>.stringify(&#123;</span><br><span class="line">        msg: body,</span><br><span class="line">        url: <span class="string">'http://localhost:3111/'</span>,</span><br><span class="line">        icon: iconUrl</span><br><span class="line">      &#125;))</span><br><span class="line">    .then(<span class="function"><span class="params">result</span> =&gt;</span> res.sendStatus(<span class="number">201</span>))</span><br><span class="line">    .catch(<span class="function"><span class="params">err</span> =&gt;</span> &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(err);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(<span class="number">3111</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'Web push app listening on port 3111!'</span>)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p><strong>service worker监听push事件，将通知详情推送给用户</strong></p>
<p>service-worker.js</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">self</span>.addEventListener(<span class="string">'push'</span>, <span class="function"><span class="keyword">function</span> <span class="params">(event)</span> </span>&#123;</span><br><span class="line"> <span class="comment">// 检查服务端是否发来了任何有效载荷数据</span></span><br><span class="line">  <span class="keyword">var</span> payload = event.data ? JSON.parse(event.data.text()) : <span class="string">'no payload'</span>;</span><br><span class="line">  <span class="keyword">var</span> title = <span class="string">'Progressive Times'</span>;</span><br><span class="line">  event.waitUntil(</span><br><span class="line">    <span class="comment">// 使用提供的信息来显示 Web 推送通知</span></span><br><span class="line">    <span class="keyword">self</span>.registration.showNotification(title, &#123;                           </span><br><span class="line">      body: payload.msg,</span><br><span class="line">      url: payload.url,</span><br><span class="line">      icon: payload.icon</span><br><span class="line">    &#125;)</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>扩展知识：<a href="https://lzw.me/a/pwa-service-worker.html#3.3" target="_blank" rel="noopener">service worker的更新</a></p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><h2 id="PWA的优势"><a href="#PWA的优势" class="headerlink" title="PWA的优势"></a>PWA的优势</h2><ul>
<li>可以将app的快捷方式放置到桌面上，全屏运行，与原生app无异</li>
<li>能够在各种网络环境下使用，包括网络差和断网条件下，不会显示undefind</li>
<li>推送消息的能力</li>
<li>其本质是一个网页，没有原生app的各种启动条件，快速响应用户指令</li>
</ul>
<h2 id="PWA存在的问题"><a href="#PWA存在的问题" class="headerlink" title="PWA存在的问题"></a>PWA存在的问题</h2><ul>
<li>依赖的GCM服务在国内无法使用</li>
<li>微信小程序的竞争</li>
</ul>
<p>尽管有上述的一些缺点，PWA技术仍然有很多可以使用的点。</p>
<ul>
<li>service worker技术实现离线缓存，可以将一些不经常更改的静态文件放到缓存中，提升用户体验。</li>
<li>service worker实现消息推送，使用浏览器推送功能，吸引用户</li>
<li>渐进式开发，尽管一些浏览器暂时不支持，可以利用上述技术给使用支持浏览器的用户带来更好的体验。</li>
</ul>
<h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><ul>
<li><a href="https://github.com/SangKa/PWA-Book-CN" target="_blank" rel="noopener">第一本 PWA 中文书</a></li>
<li><a href="https://github.com/deanhume/progressive-web-apps-book" target="_blank" rel="noopener">PWA 英文书</a></li>
<li><a href="https://lzw.me/a/pwa-service-worker.html" target="_blank" rel="noopener">网站渐进式增强体验(PWA)改造：Service Worker 应用详解</a></li>
<li><a href="https://googlechrome.github.io/samples/service-worker/basic/index.html" target="_blank" rel="noopener">Basic Service Worker Sample</a></li>
<li><a href="https://www.w3ctech.com/topic/866" target="_blank" rel="noopener">【翻译】Service Worker 入门</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Manifest" target="_blank" rel="noopener">Web App Manifest</a></li>
<li><a href="https://developer.mozilla.org/zh-CN/docs/Web/Manifest" target="_blank" rel="noopener">Service Workers: an Introduction</a></li>
<li><a href="https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/" target="_blank" rel="noopener">The Offline Cookbook</a></li>
<li><a href="https://blog.csdn.net/baidu_browser/article/details/64440238" target="_blank" rel="noopener">微信小程序和PWA对比分析</a></li>
<li><a href="https://x5.tencent.com/tbs/guide/serviceworker.html" target="_blank" rel="noopener">Service Worker最佳实践</a></li>
</ul>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/blog/tags/PWA学习与实践/" rel="tag"># PWA学习与实践</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/blog/2018/09/27/【PWA学习与实践】-1-2018，开始你的PWA学习之旅/" rel="next" title="【PWA学习与实践】(1) 2018，开始你的PWA学习之旅">
                <i class="fa fa-chevron-left"></i> 【PWA学习与实践】(1) 2018，开始你的PWA学习之旅
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/blog/2018/09/28/简单聊计算/" rel="prev" title="简单聊计算">
                简单聊计算 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            Table of Contents
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            Overview
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">lhweb</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/blog/archives/">
              
                  <span class="site-state-item-count">39</span>
                  <span class="site-state-item-name">posts</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/blog/categories/index.html">
                  <span class="site-state-item-count">6</span>
                  <span class="site-state-item-name">categories</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/blog/tags/index.html">
                  <span class="site-state-item-count">25</span>
                  <span class="site-state-item-name">tags</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/blog/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#一、背景"><span class="nav-number">1.</span> <span class="nav-text">一、背景</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#二、What’s-PWA"><span class="nav-number">2.</span> <span class="nav-text">二、What’s PWA?</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#三、PWA的实现"><span class="nav-number">3.</span> <span class="nav-text">三、PWA的实现</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Manifest实现添加至主屏幕"><span class="nav-number">3.1.</span> <span class="nav-text">Manifest实现添加至主屏幕</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#service-worker实现离线缓存"><span class="nav-number">3.2.</span> <span class="nav-text">service worker实现离线缓存</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#什么是service-worker"><span class="nav-number">3.2.1.</span> <span class="nav-text">什么是service worker</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#HTTP缓存与service-worker缓存"><span class="nav-number">3.2.2.</span> <span class="nav-text">HTTP缓存与service worker缓存</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#实现离线缓存"><span class="nav-number">3.2.3.</span> <span class="nav-text">实现离线缓存</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#调试相关"><span class="nav-number">3.2.4.</span> <span class="nav-text">调试相关</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#serice-worker实现消息推送"><span class="nav-number">3.2.5.</span> <span class="nav-text">serice worker实现消息推送</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#总结"><span class="nav-number">4.</span> <span class="nav-text">总结</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#PWA的优势"><span class="nav-number">4.1.</span> <span class="nav-text">PWA的优势</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#PWA存在的问题"><span class="nav-number">4.2.</span> <span class="nav-text">PWA存在的问题</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#参考文档"><span class="nav-number">4.3.</span> <span class="nav-text">参考文档</span></a></li></ol></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2019</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">lhweb</span>

  
</div>


  <div class="powered-by">Powered by <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a></div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">Theme &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/blog/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/blog/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/blog/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/blog/js/src/motion.js?v=5.1.4"></script>



  
  

  
  <script type="text/javascript" src="/blog/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/blog/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/blog/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  

  

  
  

  

  

  

</body>
</html>
